package cn.lingyangwl.framework.mybatis.mate.fieldunique;

import cn.lingyangwl.framework.core.utils.servlet.ServletUtils;
import cn.lingyangwl.framework.data.mate.annotations.FieldBind;
import cn.lingyangwl.framework.mybatis.mate.annotations.FieldUnique;
import cn.lingyangwl.framework.mybatis.mate.annotations.FieldUniqueConfig;
import cn.lingyangwl.framework.mybatis.mate.enums.OperationTypeEnum;
import cn.lingyangwl.framework.mybatis.mate.mapper.MybatisCommonMapper;
import cn.lingyangwl.framework.tool.core.CollectionUtils;
import cn.lingyangwl.framework.tool.core.ObjectUtils;
import cn.lingyangwl.framework.tool.core.SqlUtils;
import cn.lingyangwl.framework.tool.core.StringUtils;
import cn.lingyangwl.framework.tool.core.exception.Assert;
import cn.lingyangwl.framework.tool.core.exception.BizException;
import com.baomidou.mybatisplus.annotation.TableName;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory;

import java.lang.reflect.Field;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author shenguangyang
 */
public class FieldUniqueCore {
    private static final Map<String, FieldUniqueInfo> fieldUniqueInfoMap = new ConcurrentHashMap<>();
    private static final DefaultObjectFactory defaultObjectFactory = new DefaultObjectFactory();
    private static final DefaultObjectWrapperFactory defaultObjectWrapperFactory = new DefaultObjectWrapperFactory();
    private static final DefaultReflectorFactory defaultReflectorFactory = new DefaultReflectorFactory();

    private static MybatisCommonMapper mybatisCommonMapper;

    private static IFieldUniqueCheckLock fieldUniqueCheckLock;

    public static void setFieldUniqueCheckLock(IFieldUniqueCheckLock fieldUniqueCheckLock) {
        FieldUniqueCore.fieldUniqueCheckLock = fieldUniqueCheckLock;
    }

    /**
     * 通过传入的是实体对象, 获取字段绑定信息, 如果不存在且传入的实体对象中含有 {@link FieldBind}
     * 就会自动创建
     *
     * @param object
     * @return
     */
    private static FieldUniqueInfo get(Object object) {
        if (object == null) {
            return null;
        }
        String key = object.getClass().getName();

        if (!fieldUniqueInfoMap.containsKey(key)) {
            synchronized (key.intern()) {
                if (!fieldUniqueInfoMap.containsKey(key)) {
                    TableName tableNameAnnotation = object.getClass().getAnnotation(TableName.class);
                    FieldUniqueConfig fieldUniqueConfig = object.getClass().getAnnotation(FieldUniqueConfig.class);
                    Assert.notNull(fieldUniqueConfig, object.getClass().getName() + " class have not @FieldUniqueConfig annotation");
                    String tableName = tableNameAnnotation == null ? StringUtils.humpToUnderline(object.getClass().getSimpleName()) : tableNameAnnotation.value();
                    List<Field> fields = ObjectUtils.getFields(object);
                    FieldUniqueInfo fieldUniqueInfo = new FieldUniqueInfo();
                    fieldUniqueInfo.setTargetClass(object.getClass());
                    fieldUniqueInfo.setTableName(tableName);
                    fieldUniqueInfo.setFieldUniqueConfig(fieldUniqueConfig);
                    fieldUniqueInfo.setTableIdName(fieldUniqueConfig.tableId());
                    fieldUniqueInfo.setEntityIdName(fieldUniqueConfig.entityId());
                    for (Field field : fields) {
                        if (field.isAnnotationPresent(FieldUnique.class)) {
                            FieldUniqueInfo.FieldUniqueDefinition fieldUniqueDefinition = new FieldUniqueInfo.FieldUniqueDefinition();
                            FieldUnique annotation = field.getAnnotation(FieldUnique.class);
                            fieldUniqueDefinition.setField(field);
                            fieldUniqueDefinition.setFieldUnique(annotation);
                            fieldUniqueInfo.add(fieldUniqueDefinition);
                        }
                    }
                    fieldUniqueInfoMap.put(key, fieldUniqueInfo);
                }
            }
        }
        return fieldUniqueInfoMap.get(key);
    }

    /**
     * 校验目标对象字段唯一性
     */
    public static CheckInfoBefore checkBefore(Object object, OperationTypeEnum operationTypeEnum) {
        // 根据查询的对象信息, 获取校验字段唯一性 sql语句
        FieldUniqueInfo fieldUniqueInfo = get(object);
        Assert.notNull(operationTypeEnum, "operationType is null");
        if (fieldUniqueInfo == null || CollectionUtils.isEmpty(fieldUniqueInfo.getFieldUniqueDefinitionMap().get(FieldUnique.Condition.NONE))) {
            return null;
        }

        Map<String, String> fieldMessageMap = new HashMap<>();
        StringBuilder sqlSb = new StringBuilder("select ");
        StringBuilder whereSql = new StringBuilder(" where ( 1 = 1 or ");
        // 使用缓存进行加锁保证线程安全
        StringBuilder cacheKeySb = new StringBuilder(fieldUniqueInfo.getTableName());
        MetaObject metaObject = MetaObject.forObject(object, defaultObjectFactory, defaultObjectWrapperFactory, defaultReflectorFactory);
        List<FieldUniqueInfo.FieldUniqueDefinition> fieldUniqueDefinitions = fieldUniqueInfo.getFieldUniqueDefinitionMap()
                .getOrDefault(FieldUnique.Condition.NONE, Collections.emptyList());
        int length = fieldUniqueDefinitions.size();
        String sqlCondition = "or ";
        for (int i = 0; i < length; i++) {
            FieldUniqueInfo.FieldUniqueDefinition fieldInfo = fieldUniqueDefinitions.get(i);
            FieldUnique fieldUnique = fieldInfo.getFieldUnique();
            String fieldName = "";
            String fieldMessage = "数据重复";
            if (fieldUnique != null) {
                fieldName = fieldUnique.field();
                fieldMessage = fieldUnique.message();
            }
            String entityFieldName = fieldInfo.getField().getName();
            String tableFieldName = StringUtils.isEmpty(fieldName) ? StringUtils.humpToUnderline(entityFieldName) : fieldName;
            sqlSb.append(tableFieldName).append(",");

            Object value = getFieldValue(fieldInfo, metaObject);
            if (Objects.isNull(value)) {
                continue;
            }
            // 校验是否存在sql注入问题
            if (SqlUtils.containsSqlInjection(String.valueOf(value))) {
                throw new BizException("illegal value [{}]", value);
            }

            sqlCondition = (i == (length - 1)) ? "" : fieldUniqueInfo.getFieldUniqueConfig().sqlCondition().getValue();
            whereSql.append(" ").append(tableFieldName).append(" = ").append("'").append(value).append("' ").append(sqlCondition);
            fieldMessageMap.put(tableFieldName, fieldMessage);
            cacheKeySb.append(entityFieldName).append(value);
        }
        String tempWhereSql = whereSql.toString();
        if (tempWhereSql.endsWith(sqlCondition)) {
            whereSql = new StringBuilder(tempWhereSql.substring(0, tempWhereSql.lastIndexOf(sqlCondition)));
        }
        whereSql.append(" ) ");
        // 不等于默写数据, 比如删除
        for (FieldUniqueInfo.FieldUniqueDefinition fieldInfo :  fieldUniqueInfo.getFieldUniqueDefinitionMap()
                .getOrDefault(FieldUnique.Condition.NE, Collections.emptyList())) {
            FieldUnique fieldUniqueNe = fieldInfo.getFieldUnique();
            String entityFieldName = fieldInfo.getField().getName();
            String headerKey = fieldUniqueNe.headerKey();
            Object value;
            String fieldName = fieldUniqueNe.field();
            if (StringUtils.isEmpty(headerKey)) {
                value = getFieldValue(fieldInfo, metaObject);
            } else {
                value = ServletUtils.getRequestOfNonNull().getHeader(headerKey);
            }
            if (Objects.isNull(value)) {
                continue;
            }

            String tableFieldName = StringUtils.isEmpty(fieldName) ? StringUtils.humpToUnderline(entityFieldName) : fieldName;
            whereSql.append(" and ").append(tableFieldName).append(" != ").append("'").append(value).append("'");
            cacheKeySb.append(entityFieldName).append(value);
        }

        // 等于某些数据, 比如校验某个用户中数据是否有重复
        for (FieldUniqueInfo.FieldUniqueDefinition fieldInfo :
                fieldUniqueInfo.getFieldUniqueDefinitionMap().getOrDefault(FieldUnique.Condition.EQ, Collections.emptyList())) {
            FieldUnique fieldUnique = fieldInfo.getFieldUnique();
            String entityFieldName = fieldInfo.getField().getName();
            String headerKey = fieldUnique.headerKey();
            String fieldName = fieldUnique.field();
            Object value;
            if (StringUtils.isNotEmpty(headerKey)) {
                value = ServletUtils.getRequestOfNonNull().getHeader(headerKey);
            } else {
                value = getFieldValue(fieldInfo, metaObject);
            }
            if (Objects.isNull(value)) {
                continue;
            }

            String tableFieldName = StringUtils.isEmpty(fieldName) ? StringUtils.humpToUnderline(entityFieldName) : fieldName;
            whereSql.append(" and ").append(tableFieldName).append(" = ").append("'").append(value).append("'");
            cacheKeySb.append(entityFieldName).append(value);
        }

        String whereSqlStr = whereSql.toString();
        // 如果是更新, 则排除自己
        if (operationTypeEnum == OperationTypeEnum.UPDATE) {
            whereSqlStr = whereSqlStr + " and " + fieldUniqueInfo.getTableIdName() + " != " + metaObject.getValue(fieldUniqueInfo.getEntityIdName());
        }
        String sql = sqlSb.substring(0, sqlSb.lastIndexOf(",")) + " from " + fieldUniqueInfo.getTableName() + whereSqlStr;

        // 完成校验
        return new CheckInfoBefore(object, sql, DigestUtils.md5Hex(cacheKeySb.toString()), fieldMessageMap, metaObject);
    }

    private static Object getFieldValue(FieldUniqueInfo.FieldUniqueDefinition fieldInfo, MetaObject metaObject) {
        FieldUnique fieldUnique = fieldInfo.getFieldUnique();
        String fieldName =fieldUnique.field();
        String fieldValue = fieldUnique.value();

        String entityFieldName = fieldInfo.getField().getName();
        Object value = StringUtils.isEmpty(fieldValue) ? metaObject.getValue(entityFieldName) : fieldValue;
        if (Objects.isNull(value) || StringUtils.isEmpty(String.valueOf(value))) {
            return null;
        }
        return value;
    }


    /**
     * 完成校验
     * 默认是单机状态, 也没有对查询结果进行缓存, 只是加锁保证了线程安全
     */
    public static void doCheck(CheckInfoBefore checkInfoBefore) {
        String sql = checkInfoBefore.getSql();
        Map<String, String> fieldMessageMap = checkInfoBefore.getFieldMessageMap();
        MetaObject metaObject = checkInfoBefore.getMetaObject();

        // 开始查询数据库
        List<Map<String, Object>> result = mybatisCommonMapper.count(sql);
        if (result != null && !result.isEmpty()) {
            // 查询的对象集合
            for (Map<String, Object> stringObjectMap : result) {
                // 遍历字段
                for (Map.Entry<String, Object> entry : stringObjectMap.entrySet()) {
                    String tableFieldName = entry.getKey();
                    Object dbFieldValue = entry.getValue();
                    Object objectFieldValue = metaObject.getValue(StringUtils.underlineToHump(tableFieldName));
                    if (objectFieldValue instanceof String) {
                        if (objectFieldValue.equals(dbFieldValue) && StringUtils.isNotEmpty(String.valueOf(objectFieldValue))) {
                            throw new BizException(fieldMessageMap.get(tableFieldName));
                        }
                    } else {
                        if (objectFieldValue == dbFieldValue && Objects.nonNull(objectFieldValue)) {
                            throw new BizException(fieldMessageMap.get(tableFieldName));
                        }
                    }
                }
            }
        }
    }

    public static void setMybatisCommonMapper(MybatisCommonMapper mapper) {
        mybatisCommonMapper = mapper;
    }

    public static IFieldUniqueCheckLock getFieldUniqueCheckLock() {
        return fieldUniqueCheckLock;
    }
}
